package net.luohuasheng.bee.proxy.core.utils;


import net.luohuasheng.bee.proxy.core.utils.thread.DefaultThreadFactory;

import java.util.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;


/**
 * @author luohuasheng
 * @date 2020/4/19 21:43
 */
public class ExpireMap<K, V> extends LinkedHashMap<K, V> {
    private static final long CHECK_TIME_SECOND = 1000;
    private final Map<K, Long> expiryMap = new HashMap<>();
    private final int expire;
    private final int maxSize;

    public ExpireMap(int expire, int maxSize) {
        super(maxSize);
        initCache();
        this.expire = expire;
        this.maxSize = maxSize;
    }

    private void initCache() {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, new DefaultThreadFactory("expireMap"));
        executor.scheduleAtFixedRate(this::clearExpireCache, CHECK_TIME_SECOND, CHECK_TIME_SECOND, TimeUnit.MILLISECONDS);
    }

    private void clearFurthestCache() {
        expiryMap.entrySet().stream().min(Map.Entry.comparingByValue()).map(Map.Entry::getKey).ifPresent(this::remove);
    }

    private void clearExpireCache() {
        List<K> keys = new ArrayList<>();
        for (Map.Entry<K, Long> entry : expiryMap.entrySet()) {
            long expiryTime = entry.getValue();
            if (System.currentTimeMillis() > expiryTime) {
                keys.add(entry.getKey());
            }
        }
        for (K key : keys) {
            remove(key);
        }
    }

    @Override
    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
        expiryMap.put(key, System.currentTimeMillis() + expire);
        return super.computeIfAbsent(key, mappingFunction);
    }


    @Override
    public void clear() {
        expiryMap.clear();
        super.clear();
    }

    @Override
    public V remove(Object key) {
        expiryMap.remove(key);
        return super.remove(key);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        if (size() > maxSize) {
            clearFurthestCache();
        }
        return false;
    }


}